Оглавление :
2.1 График соотношение видов объектов общественного питания по количеству
2.2 График соотношение сетевых и несетевых заведений по количеству
2.5 Среднее количество посадочных мест в заведениях различных типов
2.6 Информация об улице из столбца address
2.7 График топ-10 улиц по количеству объектов общественного питания.
!pip install plotly
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
from plotly import graph_objects as go
import requests
import re
from bs4 import BeautifulSoup
data = pd.read_csv('projects/datasets/rest_data.csv')
data.sample(10)
data.head(10)
data.info()
data.duplicated().sum()
data['object_name'] = data['object_name'].str.lower()
data.drop_duplicates(subset=['object_name', 'address'])
data['object_type'].unique()
data['chain'].unique()
data.sample(10)
Изучили данные и проверили их на дубликаты.
object_type_count = data['object_type'].value_counts().reset_index()
object_type_count.columns = ['object_type','objects_number']
object_type_count
fig = go.Figure()
pull = [0]*len(object_type_count['objects_number'])
pull[object_type_count['objects_number'].tolist().index(object_type_count['objects_number'].max())] = 0.1
fig.add_trace(go.Pie(labels=object_type_count['object_type'], values=object_type_count['objects_number'], pull=pull))
fig.update_layout(
title='График соотношения видов объектов общественного питания по количеству',
title_x = 0.5,
margin=dict(l=50, r=50, t=130, b=50))
fig.show()
40% объектов общественного питания — кафе. Почти такое же количество совокупно столовых, ресторанов и предприятий быстрого обслуживания.
data['chain'].value_counts()
plt.figure(figsize=(16,8))
plt.xlabel('Принадлежность к сети')
plt.ylabel('Количество объектов')
plt.title('График соотношения сетевых и не сетевых заведений по количеству')
data['chain'].value_counts().plot(kind='bar', color = '#5352ed')
plt.show();
plt.figure(figsize=(16,8))
sns.stripplot(x='chain', y='number', data=data)
plt.xlabel('Вид объекта общественного питания')
plt.ylabel('Количество объектов')
plt.title('График соотношения сетевых и не сетевых заведений по количеству мест')
plt.show();
80% заведений — это не сетевые объекты, количество посадочных мест у них значительно больше, чем в сетевых заведениях.
chain_rest = data.query('chain == "да"')
chain_rest_count = chain_rest['object_type'].value_counts().reset_index()
chain_rest_count.columns = ['object_type','chain_objects_number']
chain_rest_count = chain_rest_count.merge(object_type_count)
chain_rest_count['part_of_chain'] = chain_rest_count['chain_objects_number']/chain_rest_count['objects_number']*100
chain_rest_count
fig, ax = plt.subplots()
plt.title('График соотношения сетевых видов объектов общественного питания по количеству')
plt.xlabel('Вид заведения')
plt.ylabel('Количество заведений')
ax.bar(chain_rest_count['object_type'], chain_rest_count['objects_number'], color='#5352ed')
ax.bar(chain_rest_count['object_type'], chain_rest_count['chain_objects_number'], color='#ffa500')
fig.set_figwidth(16)
fig.set_figheight(8)
plt.xticks(rotation=45)
plt.show()
Чаще всего сетевыми становятся предприятия быстрого обслуживания(фастфуд?) — 41% сетевых заведений. 28.5% магазинов (отделов кулинарии), 23.8% ресторанов и 22.9% кафе относятся к сетевым.
chain_rest
chain_rest_group = chain_rest.groupby('object_name', as_index=False).agg({'id':'count', 'number':'mean'})
chain_rest_group
plt.title('Количество посадочных мест в сетевых заведениях')
plt.xlabel('Количество посадочных мест')
plt.ylabel('Количество сетевых заведений')
chain_rest['number'].hist(figsize=(16,8), color = '#5352ed')
plt.show();
sns.set()
plt.figure(figsize=(16, 8))
sns.boxplot(x=chain_rest['number'], color='#5352ed')
plt.title('Среднее количество посадочных мест в сетевых заведениях')
plt.xlabel('Количество посадочных мест');
sns.set()
g = sns.jointplot(x='number', y='id', data=chain_rest_group, color='#5352ed', height=8)
plt.suptitle('Среднее количество посадочных мест в сетевых заведениях', y=1.05)
g.set_axis_labels('Количество заведений в сети', 'Среднее количество посадочных мест');
Сетевых заведений с большим числом посадочных мест достаточно мало, в основной массе заведений посадочных мест до 100. Заведений в сети может быть достаточно много, но количество посадочных мест небольшое. Встречаются редкие заведения, где ресторанов в сети мало, зато в каждом очень много мест.
grp_order = data.groupby('object_type').agg({'number':'mean'}).sort_values(by='number').index
sns.set()
plt.figure(figsize=(16, 8))
plt.title('Среднее количество посадочных мест для разных видов объектов общественного питания')
sns.barplot(x='object_type', y='number', data=data, order=grp_order)
plt.xticks(rotation=45)
plt.xlabel('Вид объекта общественного питания')
plt.ylabel('Среднее количество посадочных мест')
plt.show();
plt.figure(figsize=(16, 8))
plt.title('Среднее количество посадочных мест для разных видов объектов общественного питания')
sns.boxplot(x='number', y='object_type', data=data)
plt.xlabel('Количество посадочных мест')
plt.ylabel('Вид объекта общественного питания')
plt.show();
Больше всего посадочных мест бывает в столовых (достаточно часто они находятся при предприятиях и заводах, где численность работников значительная). Также можно встретить рестораны с большим числом посадочных мест, например, в некоторых заведениях есть банкетные залы и они специализируются на проведении массовых мероприятий (корпоративы, свадьбы и т.д).
streets_names = ['улица', 'переулок', 'ул', 'проспект', 'шоссе', 'проезд', 'тупик', 'линия', 'просек',
'бульвар', 'проезд', 'набережная', 'площадь', 'вал', 'аллея', 'поселок', 'деревня', 'километр', 'село']
data['address'] = data['address'].str.replace('ё','е')
def get_street(address):
for i in [1,0,2]:
for address_part in address.split(',')[i].split(' '):
if address_part in streets_names:
street = address.split(', ')[i]
return street
data['street'] = data['address'].apply(get_street)
data.sample(20)
data['street'].isnull().sum()
data[data['street'].isnull()].sample(20)
В таблице осталось 243 адреса, для которых значение улицы выделить не удалось. Это пригороды Москвы, например, Зеленоград, где идет наименование адреса с помощью 4-значного кода. Заведений там достаточно мало, поэтому оставим их со значением None.
top_eat_street = data['street'].value_counts().head(10)
eat_street = data.groupby('street', as_index=False)['id'].count().sort_values('id', ascending=False).reset_index(drop=True)
eat_street
top_eat_street = eat_street[:10]
top_eat_street
fig = px.bar(top_eat_street, x='street', y='id', title='Топ-10 улиц с наибольшим количеством заведений')
fig.update_xaxes(tickangle=45)
fig.update_layout(legend_orientation="h",
xaxis_title='Название улицы',
yaxis_title='Количество заведений')
fig.show();
street_name = ['проспект Мира', 'Профсоюзная улица', 'Ленинградский проспект', 'Пресненская набережная', 'Варшавское шоссе',
'Ленинский проспект', 'проспект Вернадского', 'Кутузовский проспект', 'Каширское шоссе', 'Кировоградская улица']
street_km = [8.9, 9.3, 5.6, 0.55, 22.5, 14, 8, 8.3, 10.5, 4.23]
table = {'street':street_name, 'street_km':street_km}
long_streets = pd.DataFrame(table)
long_streets
top_eat_street = top_eat_street.merge(long_streets)
top_eat_street['rest_per_km'] = top_eat_street['id']/top_eat_street['street_km']
top_eat_street
fig = px.bar(top_eat_street, x='street', y='rest_per_km', title='Концентрация заведений на популярных улицах')
fig.update_xaxes(tickangle=45)
fig.update_layout(legend_orientation="h",
xaxis_title='Название улицы',
yaxis_title='Количество ресторанов на 1 километр')
fig.show()
def get_coords(address):
BASE_URL = 'https://geocode-maps.yandex.ru/1.x/?apikey=bbe2afa2-bead-4eb2-807a-0e7ea42005ae&geocode=Москва,'+address
try:
req_text = requests.get(BASE_URL).text
found_pos = re.findall('<pos>\d\d.\d* \d\d.\d*', req_text)
position = found_pos[0]
position = position[5:]
return position
except Exception:
return None
top_10_eat_street = list(top_eat_street['street'])
top_10_rest_street = data[data['street'].isin(top_10_eat_street)]
top_10_rest_street
Чтобы не приходилось ждать каждый раз, когда отработает код (время ожидания от 2 минут до 6 минут), будем открывать предварительно сохраненные данные:
#CPU times: user 29.4 s, sys: 2.12 s, total: 31.5 s
#Wall time: 6min 46s
#top_10_rest_street['coords'] = top_10_rest_street['address'].apply(get_coords)
#top_10_rest_street.to_csv('top_10_rest_street.csv', index=False)
#top_10_rest_street = pd.read_csv('top_10_rest_street.csv')
url = 'https://drive.google.com/file/d/12--anhtvZIEZeVAOraO5IKpQEwSW9OGa/view?usp=sharing'
path = 'https://drive.google.com/uc?export=download&id='+url.split('/')[-2]
top_10_rest_street = pd.read_csv(path)
top_10_rest_street
top_10_rest_street[top_10_rest_street['coords'].isnull()]
Для одного адреса из 1508 получить координаты автоматически не получилось, добавим их руками:
top_10_rest_street.loc[top_10_rest_street['object_name'] == 'комбинат питаная вагш', 'coords'] = top_10_rest_street\
.loc[top_10_rest_street['object_name'] == 'комбинат питаная вагш', 'coords']\
.astype(str).replace('None', '37.471524 55.649992', regex=True)
Получим районы Москвы на основе ранее вытянутых координат:
def get_district(coords):
BASE_URL = 'https://geocode-maps.yandex.ru/1.x/?apikey=bbe2afa2-bead-4eb2-807a-0e7ea42005ae&geocode='+coords+'&kind=district&results=1'
try:
req_text = requests.get(BASE_URL).text
soup = BeautifulSoup(req_text, 'lxml')
found_district = soup.find_all('dependentlocalityname')
found_district = found_district[1]
district = found_district.text
return district
except Exception:
return None
#top_10_rest_street['district'] = top_10_rest_street['coords'].apply(get_district)
#top_10_rest_street.to_csv('top_10_rest_street_with_districts.csv', index=False)
#top_10_rest_street_with_districts = pd.read_csv('top_10_rest_street_with_districts.csv')
url = 'https://drive.google.com/file/d/1jMGk_zyxCQdsV9s9AYTt0XmfqRhYcJWg/view?usp=sharing'
path = 'https://drive.google.com/uc?export=download&id='+url.split('/')[-2]
top_10_rest_street_with_districts = pd.read_csv(path)
top_10_rest_street_with_districts
top_10_rest_district = top_10_rest_street_with_districts.groupby(['street', 'district'])['street'].nunique()
top_10_rest_district
Улицы, где расположено максимальное количество заведений, достаточно разные по длине и чаще всего проходят через несколько районов. Заметим, что улицы достаточно близки к центру и уходят на юг города.
loosers_street = eat_street.query('id == 1')
loosers_street
#CPU times: user 11.2 s, sys: 854 ms, total: 12.1 s
#Wall time: 1min 48s
#import warnings
#warnings.filterwarnings("ignore")
#loosers_street['coords'] = loosers_street['street'].apply(get_coords)
#loosers_street.to_csv('loosers_street_with_coords.csv', index=False)
#loosers_street = pd.read_csv('loosers_street_with_coords.csv')
url = 'https://drive.google.com/file/d/1jc-fp1wpFj9P3ghcHiBX5U_4w3lR2gDf/view?usp=sharing'
path = 'https://drive.google.com/uc?export=download&id='+url.split('/')[-2]
loosers_street = pd.read_csv(path)
loosers_street
#CPU times: user 12.9 s, sys: 838 ms, total: 13.8 s
#Wall time: 2min 37s
#import warnings
#warnings.filterwarnings("ignore")
#loosers_street['district'] = loosers_street['coords'].apply(get_district)
#loosers_street.to_csv('loosers_street_with_district.csv', index=False)
#loosers_street_with_district = pd.read_csv('loosers_street_with_district.csv')
url = 'https://drive.google.com/file/d/1WtY6I83nGUUKKuRkeloF4bDP9FEo05Wp/view?usp=sharing'
path = 'https://drive.google.com/uc?export=download&id='+url.split('/')[-2]
loosers_street_with_district = pd.read_csv(path)
loosers_street_with_district
loosers_district = loosers_street_with_district['district'].value_counts().head(10)
loosers_district
В центральных районах Москвы достаточно много улиц, на которых находится всего 1 заведение общественного питания. Всего в Москве 584 улицы с 1 заведением.
top_10_rest_street.describe()
plt.figure(figsize=(16, 8))
plt.title('Распределение количества посадочных мест для улиц с большим количеством объектов общественного питания')
ax = sns.boxplot(x='number', y='street', data=top_10_rest_street)
plt.xlabel('Количество посадочных мест')
plt.ylabel('Название улицы')
plt.show()
top_10_rest_street = top_10_rest_street.groupby('street')['number'].sum().reset_index()
top_10_rest_street = top_10_rest_street.merge(long_streets)
top_10_rest_street['numbers_per_km'] = top_10_rest_street['number']/top_10_rest_street['street_km']
top_10_rest_street
fig = px.bar(top_10_rest_street, x='street', y='numbers_per_km', title='Концентрация посадочных мест на популярных улицах')
fig.update_xaxes(tickangle=45)
fig.update_layout(legend_orientation="h",
xaxis_title='Название улицы',
yaxis_title='Количество посадочных мест на 1 километр')
fig.show()
Максимальное количество посадочных мест находится на проспекте Мира, но важно помнить, что улица длинная. Если рассчитать показатель «Количество посадочных мест на 1 километр», то вперёд вырывается Пресненская набережная, на 550 метрах находится 7656 посадочных мест в заведении.
почти 40% рынка — это кафе;
80% заведений — это не сетевые объекты, количество посадочных мест у них значительно больше, чем в сетевых заведениях;
чаще всего сетевыми становятся предприятия быстрого обслуживания — 41% сетевых заведений. Из кафе к сетевым относятся 22.9% заведений;
больше всего посадочных мест бывает в столовых, а в кафе средняя цифра — 40 мест;
улицы, где расположено максимальное количество заведений, достаточно разные по длине и проходят через несколько районов. Пресненская набережная — исключение, она короткая и там максимальная концентрация заведений;
в Москве больше 20 районов, где находится всего 1 заведение общественного питания, и 584 улицы, на которых 1 заведение.
Кафе, для которого проводилось исследование, оригинальное и дорогое, поэтому думать про сетевой формат сложно и рано. В среднем в кафе около 40 посадочных мест, но если оно уникальное, можно сделать меньше посадочных мест, а ажиотаж создать атмосферой. Для принятия решения о выборе района или улицы нужно больше данных, например, стоимость аренды, платежеспособность жителей этого района и их количество. На первый взгляд, хорошим вариантом будет расположить кафе в Сити, чтобы это находило отклик в атмосфере заведения. Однако на Пресненской набережной сейчас максимальная концентрация заведений. Вероятно, можно рассмотреть места с видом на Сити (кафе на Эйфелевой башне менее привлекательно, чем кафе с видом на неё).